package com.cirnoworks.uhs.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.Selector;
import java.util.HashSet;
import java.util.Random;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cirnoworks.common.Hasher;
import com.cirnoworks.uhs.Link;
import com.cirnoworks.uhs.LinkContainer;
import com.cirnoworks.uhs.LinkManager;
import com.cirnoworks.uhs.Protocal;

@Singleton
public class Client extends LinkContainer implements LinkManager {

	private final static Logger LOG = LoggerFactory.getLogger(Client.class);

	String handShakerHost;
	int handShakerPort;
	byte[] userKey;

	ClientLink[] links;

	@Inject
	Hasher hasher;

	ClientMode mode;

	SocketAddress handShakerAddress;

	@Inject
	void configureService(@Named("uhs.client.mode") String mode,
			@Named("uhs.client.server") String address,
			@Named("uhs.client.port") String sPort,
			@Named("uhs.client.user") String user,
			@Named("uhs.client.password") String password,
			@Named("uhs.client.services") String sServices) throws IOException {
		this.mode = ClientMode.valueOf(mode.toUpperCase());
		handShakerHost = address;
		handShakerPort = Integer.parseInt(sPort);
		userKey = hasher.encode(user + password + ".!@");
		String[] services = sServices.split(",");
		int count = services.length;
		if (count == 0) {
			throw new RuntimeException("Empty service configurations");
		}

		links = new ClientLink[count];

		for (int i = 0, max = count; i < max; i++) {
			String[] service = services[i].trim().split("[\\:\\@]");
			if (service.length != 4) {
				throw new RuntimeException("Illegal service ["
						+ services[i].trim() + "]");
			}
			links[i] = new ClientLink(selector, this, userKey, service[0],
					Integer.parseInt(service[1]), Integer.parseInt(service[2]),
					Integer.parseInt(service[3]));
		}
	}

	private long lastHandShakedTime;
	private final Random rand = new Random();
	private final ByteBuffer buf = ByteBuffer.allocate(65536);

	@Override
	public void beforeSelect() {
		try {
			long now = System.currentTimeMillis();
			if (now - lastHandShakedTime > 10000) {
				lastHandShakedTime = now;
				// handshake
				handShakerAddress = new InetSocketAddress(handShakerHost,
						handShakerPort);
				for (ClientLink link : links) {
					buf.clear();
					Protocal.CmdPortMap.request(buf, rand.nextLong(), userKey,
							link.getServicePort(), link.getRedirectorPort());
					buf.flip();
					// LOG.info("Handshaking with " + handShakerAddress
					// + " for port=" + link.getServicePort());
					link.setHandShakerAddress(handShakerAddress);
					link.sendData(selector, handShakerAddress, buf);
				}
			}
		} catch (ClosedChannelException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void afterSelect() {
		for (int i = 0, max = links.length; i < max; i++) {
			links[i].checkSessions();
		}
	}

	private HashSet<Link> managedLinks = new HashSet<Link>();

	@Override
	public void registerLink(Link link) {
		managedLinks.add(link);
	}

	@Override
	public void onLinkClosed(Link link) {
		managedLinks.remove(link);
	}

	@Override
	protected void closeLinks(Selector selector) {
		LOG.info("Closing links..." + managedLinks.size());
		Link[] allLinks = managedLinks.toArray(new Link[managedLinks.size()]);
		for (Link link : allLinks) {
			try {
				link.close();
			} catch (Exception e) {
				LOG.error("Error closing link" + link, e);
			}
		}
		LOG.info("Closing links...done");
	}
}
